package Element.Geom;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;

import Element.*;
import DistSys.*;

public abstract class AbsElemGeom {
    //every 3 digits for an entry in a level of directory tree

    /**
     * The name
     * @deprecated  Use this String only if you REALLY know what you are doing.
     *              Use <code>setName</code> and <code>getName</code> instead to
     *              ensure data integrity.
     */
    protected String name;
    /**
     * The description
     * @deprecated  Use this String only if you REALLY know what you are doing.
     *              Use <code>setDesc</code> and <code>getDesc</code> instead to
     *              ensure data integrity.
     */
    protected String desc;  //description

    public int x1Stored, y1Stored, x2Stored, y2Stored;
    protected int  rotation;       // rotation parameter: 0,1,2,3
    protected int  LTC;
    protected boolean showName;
    protected boolean showDesc;
    public static final int SHOW_DATA   = 0;
    public static final int SHOW_RESULT = 1;
    public static final int NUM_SHOW    = 2;
    protected int[][] showIndex;


    public int x1,y1,x2,y2;   //screen value (physical resolution)
    public double group;
    public Rectangle frame;
    public boolean  isSelected;       // in the selected list or not
    public AffineTransform t;

    // connectivity data
    public Vector conn1,    //references of connected elem at node 1
                  conn2;    //references of connected elem at node 2

    public int sourceNode;  //=1,2
    public AbsElem parent;
    public Vector  childList;

    // Unbalanced Phase Multipliers
    public static final double [] ubiMult = {1,3,1.5,1};  //Current Multiplier
    public static final double [] ubzMult = {1,2,2,1};  //Impedance Multiplier


    // container class
    // public AbsElem container;  // this will be set in constructers of concrete element classes

    /*===============================================
    Constructor
    ===============================================*/
    public AbsElemGeom( ) {
        name = " ";
        desc=" ";
        rotation=1;   // 1==SOUTH
        LTC = 0;
        showName = false;
        showDesc = false;
        showIndex = new int[NUM_SHOW][];

        group = -1;   //After output to engines, group += 1;
        frame = new Rectangle();
        isSelected =false;
        t = new AffineTransform();

        conn1 = new Vector(4,4);
        conn2 = new Vector(4,4);
        sourceNode=0;
        parent =null;
        childList = new Vector(4,4);
    }

    protected void init( String n) {
        this.name = n;
    }

    protected void init( String n, String desc, int r, int LTC,
                        boolean sn, boolean sd, int[][] si) {
        this.name = n;
        this.desc = desc;
        this.rotation = r;  // be default r=0, then rotation = 1 = SOUTH
        this.LTC = LTC;

        showName = sn;
        showDesc = sd;
        showIndex = si;
    }

    /*==============================================
    set/get methods
    ==============================================*/
    public void setName(String s) { name = s; }
    public String getName() { return name; }

    public void setDesc(String s) { desc = s; }
    public String getDesc() { return desc; }

    public void setRotation(int r) { rotation = r % 4; }
    public int getRotation() { return rotation; }

    public void setLTC(int ltc) { LTC = ltc; }
    public int getLTC() { return LTC; }

    public boolean getShowName() { return showName; }
    public boolean getShowDesc() { return showDesc; }
    public boolean getShowIndex(int iCat) { return (showIndex[iCat] != null); }
    public boolean getShowAnything() {
        return showName || showDesc ||
            showIndex[SHOW_DATA] != null || showIndex[SHOW_RESULT] != null;
    }
    public int getShowIndexCount(int iCat) { return showIndex[iCat].length; }
    public int getShowIndex(int iCat, int index) {
        return showIndex[iCat][index];
    }

    public void setShowName(boolean bShow) { showName = bShow; }
    public void setShowDesc(boolean bShow) { showDesc = bShow; }
    public void setShowIndex(int iCat, int[] aiShow) {
        if (aiShow == null || aiShow.length == 0)
            showIndex[iCat] = null;
        else {
            showIndex[iCat] = new int[aiShow.length];
            System.arraycopy(aiShow, 0, showIndex[iCat], 0, aiShow.length);
        }
    }


    /*==================================================
    set coincidantes from screen values
    ==================================================*/
    public void setNode1FromScreen(int i, int j){
        x1Stored=(int) Math.round(((float)i)/Sys.prjInfo.dZoomFactor);
        y1Stored=(int) Math.round(((float)j)/Sys.prjInfo.dZoomFactor);
        x1 = (int) Math.round(x1Stored * Sys.prjInfo.dZoomFactor);
        y1 = (int) Math.round(y1Stored * Sys.prjInfo.dZoomFactor);
    }

    public void setNode1FromScreen(Point pt){
        setNode1FromScreen(pt.x, pt.y);
    }

    public void setNode2FromScreen(int i, int j){
        x2Stored=(int) Math.round(((float)i)/Sys.prjInfo.dZoomFactor);
        y2Stored=(int) Math.round(((float)j)/Sys.prjInfo.dZoomFactor);
        x2 = (int)Math.round(x2Stored * Sys.prjInfo.dZoomFactor);
        y2 = (int) Math.round(y2Stored * Sys.prjInfo.dZoomFactor);
    }

    public void setNode2FromScreen(Point pt){
        setNode2FromScreen(pt.x, pt.y);
    }


    /*==================================================
    set coincidants for nodes from stored values;
    ==================================================*/
    public void setNode1FromStored( int i, int j){
        x1Stored=i; y1Stored=j;
        x1=(int) Math.round( ((float)i) * Sys.prjInfo.dZoomFactor);
        y1=(int) Math.round( ((float)j) * Sys.prjInfo.dZoomFactor);
    }

    public void setNode1FromStored( Point p){
        setNode1FromStored( p.x, p.y);
    }

    public void setNode2FromStored( int i, int j){
        x2Stored=i; y2Stored=j;
        x2=(int) Math.round(((float)i)*Sys.prjInfo.dZoomFactor);
        y2=(int) Math.round(((float)j)*Sys.prjInfo.dZoomFactor);
    }

    public void setNode2FromStored( Point p){
        setNode2FromStored( p.x, p.y);
    }

    /*==================================================
    getter methods
    ==================================================*/
    public int getX1(){ return x1;}
    public int getY1(){ return y1;}

    public int getX1Stored(){ return x1Stored;}
    public int getY1Stored(){ return y1Stored;}

    public int getX2(){ return x2;}
    public int getY2(){ return y2;}

    public int getX2Stored(){ return x2Stored;}
    public int getY2Stored(){ return y2Stored;}

    public void setRotate( int r) { rotation =r;}
    public int getRotate() { return rotation; }


    /*==================================================
    misc. methods
    ==================================================*/

    //x, y are screen values
    public boolean isStartPoint(int x, int y, int snap) {
        if( x >= (x1-snap) && x <= (x1+snap) && y >= (y1-snap) && y <= (y1+snap) )
            return true;
        //if( x == x1 && y == y1 ) return true;
        else
            return false;
    }

    //x, y are screen values
    public boolean isStartPoint(Point pt, int snap){
         return isStartPoint(pt.x, pt.y, snap);
    }

    //x, y are screen values
    public boolean isEndPoint(int x, int y, int snap) {
        if( x>=(x2-snap) && x<=(x2+snap) && y>=(y2-snap) && y<= (y2+snap) )
            return true;
        else
            return false;
    }

    //x, y are screen values
    public boolean isEndPoint(Point pt, int snap){
         return isEndPoint(pt.x, pt.y, snap);
    }


    public void setSelected(boolean b){ isSelected = b;}

    public boolean isFullyInARect( Rectangle r){
        if( r.contains(x1,y1) == false ) { return false;}
        if( r.contains(x2,y2) == false ) { return false;}
        return true;
    }

    public boolean isPartlyInARect( Rectangle r){
        return r.intersectsLine(x1,y1,x2,y2) || r.intersects( this.frame);
        //     for line or bus;                 //for all other elements
    }

    public void scaleCoord(double s){
        x1 = (int) Math.round(((double) x1Stored) * s);
        y1 = (int) Math.round(((double) y1Stored) * s);
        x2 = (int) Math.round(((double) x2Stored) * s);
        y2 = (int) Math.round(((double) y2Stored) * s);
        calcFrame();
    }

    // find (x2,y2) from (x1,y1) and (initDirection + rotate)
    //input : initDirection, rotation, Sys.prjInfo.dZoomFactor, (x1,y1)
    //output:
    public void findNode2BasedonDir(boolean bDefaultSize) {
        int dftDist;  //distance between (x1,y1) and (x2,y2)
        if (bDefaultSize) {
            if( this instanceof OneNodeElemGeom) { dftDist = 3; }
            else { dftDist = 4; }
        } else {
            dftDist = (int)Math.sqrt((x2Stored-x1Stored) * (x2Stored-x1Stored) +
                                     (y2Stored-y1Stored) * (y2Stored-y1Stored));
        }
        switch (rotation%4) {
            case 0: x2Stored = x1Stored + dftDist; y2Stored = y1Stored;            break;
            case 1: x2Stored = x1Stored;           y2Stored = y1Stored + dftDist;  break;
            case 2: x2Stored = x1Stored - dftDist; y2Stored = y1Stored;            break;
            case 3: x2Stored = x1Stored ;          y2Stored = y1Stored - dftDist;  break;
            default:
                System.out.println("Error @ AbsElemGeom:findNode2BasedOnDir()");
                break;
            //furthor work: need to judge: out of drawing boundary?
        }
        x2 = (int) Math.round( x2Stored * Sys.prjInfo.dZoomFactor);
        y2 = (int) Math.round( y2Stored * Sys.prjInfo.dZoomFactor);
    }


    /*==================================================
    need to be overridden by Line, Cable, Bus, Group
    ==================================================*/
    //not used for checking connectivity which needs stored values
    public boolean coverPoint(int x, int y, int snap){
        if( x >= frame.x && x <= (frame.x+frame.width) &&
            y >= frame.y && y <= (frame.y+frame.height) )
            return true;
        else if( isStartPoint(x,y, snap) == true || isEndPoint(x,y,snap)==true )
            return true;
        else
            return false;
    }

    public void incRotate(){  // rename it to rotateElem
        rotation ++;
        rotation %=4;
        findNode2BasedonDir(false); // no default size
        calcFrame();
    }

    public void calcFrame(){
        double dist  = Math.sqrt( (double)((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ) );
        Point C= new Point( (x1+x2)/2, (y1+y2)/2);
        frame.width = (int) (dist/2.0);
        frame.x = C.x - frame.width;
        frame.y = C.y - frame.width;
        frame.width *=2;
        frame.height = frame.width;
    }

    public void toggle() {   /**@todo: implement this Element.AbsElem abstract method*/
        // N/A to Gen
    }



    //This copyTo() will be used by clone() method of its subclass
    //AbsElemGeom can't implement Cloneable interface since it is an abstract class
    public void copyTo( AbsElemGeom g){
        g.setName(new String(this.name));
        g.setDesc(new String(this.desc));

        //g.iUid = this.iUid;  // doesn't matter; may ask Sys to generate one
        g.group = this.group;
        g.rotation=this.rotation;
        g.showName = this.showName;
        g.showDesc = this.showDesc;
        g.showIndex = new int[NUM_SHOW][];
        for (int iCat = 0; iCat < NUM_SHOW; iCat++) {
            if (this.showIndex[iCat] != null) {
                g.showIndex[iCat] = new int[this.showIndex[iCat].length];
                System.arraycopy(this.showIndex[iCat], 0, g.showIndex[iCat], 0,
                    this.showIndex[iCat].length);
            } else
                g.showIndex[iCat] = null;
        }

        g.x1Stored = this.x1Stored;
        g.y1Stored = this.y1Stored;
        g.x2Stored = this.x2Stored;
        g.y2Stored = this.y2Stored;
        g.x1 = (int) Math.round( g.x1Stored * Sys.prjInfo.dZoomFactor );
        g.y1 = (int) Math.round( g.y1Stored * Sys.prjInfo.dZoomFactor );
        g.x2 = (int) Math.round( g.x2Stored * Sys.prjInfo.dZoomFactor );
        g.y2 = (int) Math.round( g.y2Stored * Sys.prjInfo.dZoomFactor );

        g.calcFrame();  //created.frame already instantiated
        g.isSelected = this.isSelected;

        g.t = (AffineTransform) this.t.clone();
        //no need to process conn1, conn2, parent, childList, sourceNode;
    }

    public void displace(int dx, int dy){
        this.x1Stored += dx;
        this.y1Stored += dy;
        this.x2Stored += dx;
        this.y2Stored += dy;
        this.x1 = (int) Math.round( this.x1Stored * Sys.prjInfo.dZoomFactor );
        this.y1 = (int) Math.round( this.y1Stored * Sys.prjInfo.dZoomFactor );
        this.x2 = (int) Math.round( this.x2Stored * Sys.prjInfo.dZoomFactor );
        this.y2 = (int) Math.round( this.y2Stored * Sys.prjInfo.dZoomFactor );
        this.calcFrame();
    }

    public double getVisualDist(){
        return Math.sqrt((double)((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)));
    }
}